/*
 *   Copyright (c) 2024 Huawei Device Co., Ltd.
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
#include <bits/alltypes.h>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <net/if.h>
#include <thread>
#include <chrono>
#include <sys/stat.h>
#include <memory>
#include <uv.h>
#include <fstream>
#include <cstdio>
#include <cstdlib>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/quota.h>
#include "napi/native_api.h"
#include "ohaudio/native_audiocapturer.h"
#include "ohaudio/native_audiorenderer.h"
#include "ohaudio/native_audiostreambuilder.h"
#include "ohaudio/native_audiostream_base.h"
#include "hilog/log.h"
#include "audioRecorder.h"
#include "config/config.h"

namespace AudioTestConstants {
constexpr int32_t FIRST_ARG_IDX = 1;
constexpr int32_t SECOND_ARG_IDX = 2;
constexpr int32_t THIRD_ARG_IDX = 3;
constexpr int32_t RECODER_TIME = 10000;
constexpr int32_t COUNTDOWN_INTERVAL = 1000;
constexpr int32_t CONVERT_RATE = 1000;
constexpr int32_t WAIT_INTERVAL = 1000;
} // namespace AudioTestConstants

const int GLOBAL_RESMGR = 0xFF00;
const char *TAG = "[Sample_audio]";
FILE *g_file = nullptr;
bool g_readEnd = false;
bool g_rendererLowLatency = false;

std::unique_ptr<std::ifstream> normalPCM;
std::unique_ptr<std::ifstream> vividPCM;
std::unique_ptr<std::ifstream> metaDataFile;

static OH_AudioStream_Result ret;
static OH_AudioCapturer *audioCapturer;
static OH_AudioRenderer *audioRenderer;
static OH_AudioStreamBuilder *builder;
static OH_AudioStreamBuilder *rendererBuilder;
static OH_AudioRenderer *audioRendererNormal;
static OH_AudioRenderer *audioRendererVivid;

static int32_t AudioCapturerOnReadData(OH_AudioCapturer *capturer, void *userData, void *buffer, int32_t bufferLen)
{
    size_t count = 1;
    if (fwrite(buffer, bufferLen, count, g_file) != count) {
        printf("buffer fwrite err");
    }
    return 0;
}

static void parseEnv(napi_env& env, napi_callback_info& info)
{
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    napi_value obj = args[0];

    napi_value napiValue;
    int32_t numValue;
    size_t strLen;

    napi_get_named_property(env, obj, "audioBitrate", &napiValue);
    napi_get_value_int32(env, napiValue, &numValue);
    Config::GetInstance().SetAudioBitrate(numValue);
    
    napi_get_named_property(env, obj, "audioChannels", &napiValue);
    napi_get_value_int32(env, napiValue, &numValue);
    Config::GetInstance().SetAudioChannels(numValue);
    
    napi_get_named_property(env, obj, "audioSampleRate", &napiValue);
    napi_get_value_int32(env, napiValue, &numValue);
    Config::GetInstance().SetAudioSampleRate(numValue);
    
    char pcmBuf[256];
    napi_get_named_property(env, obj, "pcmpath", &napiValue);
    napi_get_value_string_utf8(env, napiValue, nullptr, 0, &strLen);
    napi_get_value_string_utf8(env, napiValue, pcmBuf, strLen + 1, &strLen);
    Config::GetInstance().SetPCMPath(pcmBuf);
    
    char amrBuf[256];
    napi_get_named_property(env, obj, "amrpath", &napiValue);
    napi_get_value_string_utf8(env, napiValue, nullptr, 0, &strLen);
    napi_get_value_string_utf8(env, napiValue, amrBuf, strLen + 1, &strLen);
    Config::GetInstance().SetAMRPath(amrBuf);
}

static napi_value AudioCapturerInit(napi_env env, napi_callback_info info)
{
    parseEnv(env, info);
    if (audioCapturer) {
        OH_AudioCapturer_Release(audioCapturer);
        OH_AudioStreamBuilder_Destroy(builder);
        audioCapturer = nullptr;
        builder = nullptr;
    }
    
    if (g_file) {
        fclose(g_file);
        g_file = nullptr;
    }

    g_file = fopen(Config::GetInstance().GetPCMPath().c_str(), "wb");
    
    // Create builder
    OH_AudioStream_Type type = AUDIOSTREAM_TYPE_CAPTURER;
    OH_AudioStreamBuilder_Create(&builder, type);
    
    // set params and callbacks
    OH_AudioStreamBuilder_SetSamplingRate(builder, Config::GetInstance().GetAudioSampleRate());
    OH_AudioStreamBuilder_SetChannelCount(builder, Config::GetInstance().GetAudioChannels());
    OH_AudioStreamBuilder_SetSampleFormat(builder, AUDIOSTREAM_SAMPLE_S16LE);
    OH_AudioStreamBuilder_SetLatencyMode(builder, AUDIOSTREAM_LATENCY_MODE_FAST);
    OH_AudioStreamBuilder_SetEncodingType(builder, AUDIOSTREAM_ENCODING_TYPE_RAW);
    
    OH_AudioCapturer_Callbacks callbacks;
    callbacks.OH_AudioCapturer_OnReadData = AudioCapturerOnReadData;
    callbacks.OH_AudioCapturer_OnInterruptEvent = nullptr;
    callbacks.OH_AudioCapturer_OnStreamEvent = nullptr;
    callbacks.OH_AudioCapturer_OnError = nullptr;
    OH_AudioStreamBuilder_SetCapturerCallback(builder, callbacks, nullptr);
    
    // create OH_AudioCapturer
    OH_AudioStreamBuilder_GenerateCapturer(builder, &audioCapturer);
    return nullptr;
}

static napi_value AudioCapturerStart(napi_env env, napi_callback_info info)
{
    OH_AudioCapturer_Start(audioCapturer);
    return nullptr;
}

static napi_value AudioCapturerPause(napi_env env, napi_callback_info info)
{
    OH_AudioCapturer_Pause(audioCapturer);
    return nullptr;
}

static napi_value AudioCapturerStop(napi_env env, napi_callback_info info)
{
    OH_AudioCapturer_Stop(audioCapturer);
    if (g_file) {
        fclose(g_file);
        g_file = nullptr;
    }
    return nullptr;
}

static napi_value AudioCapturerRelease(napi_env env, napi_callback_info info)
{
    if (audioCapturer) {
        OH_AudioCapturer_Release(audioCapturer);
        OH_AudioStreamBuilder_Destroy(builder);
        audioCapturer = nullptr;
        builder = nullptr;
    }
    if (g_file) {
        fclose(g_file);
        g_file = nullptr;
    }
    return nullptr;
}

static napi_value AudioEncode(napi_env env, napi_callback_info info)
{
    SampleInfo sampleInfo;
    sampleInfo.audioCodecMime = OH_AVCODEC_MIMETYPE_AUDIO_AMR_NB;
    sampleInfo.audioSampleForamt = SAMPLE_S16LE;
    sampleInfo.audioSampleRate = Config::GetInstance().GetAudioSampleRate();
    sampleInfo.audioChannelCount = Config::GetInstance().GetAudioChannels();
    sampleInfo.audioBitRate = Config::GetInstance().GetAudioBitrate();
    sampleInfo.audioChannelLayout = CH_LAYOUT_MONO;
    
    AudioRecorder::GetInstance().Init(sampleInfo);
    
    int32_t outputFd = open(Config::GetInstance().GetAMRPath().c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
    std::shared_ptr<Muxer> muxer = std::make_unique<Muxer>();
    int32_t ret = muxer->Create(outputFd);
    ret = muxer->AuidoConfig(sampleInfo);
    ret = muxer->Start();
    
    AudioRecorder::GetInstance().Start(muxer);
    return nullptr;
}

struct CallbackContext {
    napi_env env = nullptr;
    napi_ref callbackRef = nullptr;
};

void Callback(void *asyncContext)
{
    uv_loop_s *loop = nullptr;
    CallbackContext *context = (CallbackContext *)asyncContext;
    napi_get_uv_event_loop(context->env, &loop);
    
    uv_work_t *work = new uv_work_t;
    work->data = context;
    uv_queue_work(
        loop, work, [](uv_work_t *work) {},
        [](uv_work_t *work, int status) {
            CallbackContext *context = (CallbackContext *)work->data;
        
            // 管理 napi_value 的生命周期，防止内存泄露
            napi_handle_scope scope = nullptr;
            napi_open_handle_scope(context->env, &scope);
        
            // 回调至UI侧
            napi_value callback = nullptr;
            napi_get_reference_value(context->env, context->callbackRef, &callback);
            napi_call_function(context->env, nullptr, callback, 0, nullptr, nullptr);
        
            napi_close_handle_scope(context->env, scope);
            delete context;
            delete work;
    });
}

static napi_value AudioEncodeAsync(napi_env env, napi_callback_info info)
{
    SampleInfo sampleInfo;
    sampleInfo.audioCodecMime = OH_AVCODEC_MIMETYPE_AUDIO_AMR_NB;
    sampleInfo.audioSampleForamt = SAMPLE_S16LE;
    sampleInfo.audioSampleRate = Config::GetInstance().GetAudioSampleRate();
    sampleInfo.audioChannelCount = Config::GetInstance().GetAudioChannels();
    sampleInfo.audioBitRate = Config::GetInstance().GetAudioBitrate();
    sampleInfo.audioChannelLayout = CH_LAYOUT_MONO;
    
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    
    auto asyncContext = new CallbackContext();
    asyncContext->env = env;
    napi_create_reference(env, args[0], 1, &asyncContext->callbackRef);
    
    sampleInfo.encodeDoneCallback = &Callback;
    sampleInfo.encodeDoneCallbackData = asyncContext;
    
    AudioRecorder::GetInstance().Init(sampleInfo);
    
    int32_t outputFd = open(Config::GetInstance().GetAMRPath().c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
    std::shared_ptr<Muxer> muxer = std::make_unique<Muxer>();
    int32_t ret = muxer->Create(outputFd);
    ret = muxer->AuidoConfig(sampleInfo);
    ret = muxer->Start();
    
    AudioRecorder::GetInstance().Start(muxer);
    return nullptr;
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        {"audioCapturerInit", nullptr, AudioCapturerInit, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"audioCapturerStart", nullptr, AudioCapturerStart, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"audioCapturerPause", nullptr, AudioCapturerPause, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"audioCapturerStop", nullptr, AudioCapturerStop, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"audioCapturerRelease", nullptr, AudioCapturerRelease, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"audioEncode", nullptr, AudioEncode, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"audioEncodeAsync", nullptr, AudioEncodeAsync, nullptr, nullptr, nullptr, napi_default, nullptr}
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
    napi_module_register(&demoModule);
}
